package de.tudarmstadt.ukp.muip.fibqdojo;

import java.util.LinkedList;
import java.util.List;

import com.google.common.base.Optional;

import de.tudarmstadt.ukp.muip.fibqdojo.interfaces.IFIBQDistractorProvider;
import de.tudarmstadt.ukp.muip.fibqdojo.interfaces.IFIBQFrequencyProvider;
import de.tudarmstadt.ukp.muip.fibqdojo.interfaces.IFIBQSentence;
import de.tudarmstadt.ukp.muip.fibqdojo.interfaces.IFIBQToken;
import de.tudarmstadt.ukp.muip.fibqdojo.util.FrequencyDistribution;
import de.tudarmstadt.ukp.muip.fibqdojo.util.NGram;
import de.tudarmstadt.ukp.muip.fibqdojo.util.NGramConverter;
import de.tudarmstadt.ukp.muip.fibqdojo.visitor.GetFormVisitor;
import de.tudarmstadt.ukp.muip.fibqdojo.visitor.GetPartOfSpeechVisitor;
import de.tudarmstadt.ukp.muip.fibqdojo.visitor.GetTokenListVisitor;

/**
 * This class works with three grams and looks for the first occurrence of a
 * token with a given part of speech tag. It is assumed that the specified part
 * of speech tag has to occur as rightmost part of speech of the three gram.
 * Depending on the tag it might happen that the first occurrence of the part of
 * speech tag is already the first or second word. In that case an empty list is
 * returned since the underlying assumption is that the seeked part of speech
 * occurs earliest as right most word of the first three gram.
 * 
 * @author Tobias Horsmann
 */
public class POSDistractorProvider implements IFIBQDistractorProvider {

    private final String partOfSpeech;
    private final String separator;
    private List<String> candidates;
    private int minDistractors = -1;
    private int maxDistractors = -1;
    private final IFIBQFrequencyProvider frequency;

    /**
     * Part of speech is the tag for which distractors shall be created. The
     * distractor provider always uses the first occurrence of a given part of
     * speech.
     */
    public POSDistractorProvider(List<IFIBQSentence> sentencePool, String partOfSpeech, IFIBQFrequencyProvider frequency) {
        this.partOfSpeech = partOfSpeech;
        this.frequency = frequency;
        this.candidates = makeCandidatePool(sentencePool);
        this.separator = " ";
    }

    @Override
    public List<String> getDistractors(IFIBQSentence currentSentence) {
        Optional<NGram> threeGram = getFirstOccurenceOfPartOfSpeechAtMostRightTokenIn3Gram(currentSentence);

        if (!threeGram.isPresent()) {
            return new LinkedList<String>();
        }

        return getDistractors(threeGram.get());
    }

    private List<String> getDistractors(NGram nGram) {
        String posPreceedingTokens = nGram.getConcatNGram(0, 1);

        List<String> candidatesDescOrder = getCandidates(posPreceedingTokens, candidates);
        IFIBQToken token = nGram.getTokenAt(2);
        GetFormVisitor visitor = new GetFormVisitor();
        token.accept(visitor);
        candidatesDescOrder = excludeCorretAnswer(candidatesDescOrder, visitor.getForm().toLowerCase());

        if (!minDistractorsFound(candidatesDescOrder.size())) {
            return new LinkedList<String>();
        }

        if (!maxDistractorsExceed(candidatesDescOrder.size())) {
            return candidatesDescOrder.subList(0, maxDistractors);
        }

        return candidatesDescOrder;
    }

    private List<String> excludeCorretAnswer(List<String> candidates, String answer) {
        List<String> cleanedCandidates = new LinkedList<String>(candidates);
        String found = null;
        for (String s : cleanedCandidates) {
            if (s.equals(answer)) {
                found = s;
                break;
            }
        }
        if (found != null) {
            cleanedCandidates.remove(found);
        }
        return cleanedCandidates;
    }

    /**
     * The current 2 gram is used with the various candidates from the pool to
     * determine whether this combination has be seen bevor. If yes, the
     * respective combination is not valid. The result is a list of remaining
     * candidates which does not form a valid combination and hence are suited
     * as distractors.
     */
    private List<String> getCandidates(String twoGram, List<String> pool) {
        List<String> validCandidates = new LinkedList<String>();
        for (String candidate : pool) {
            String key = twoGram + separator + candidate;
            if (frequency.getFrequency(key) == 0) {
                validCandidates.add(candidate);
            }
        }
        return validCandidates;
    }

    private boolean maxDistractorsExceed(int size) {
        if (maxDistractors > -1) {
            return size <= maxDistractors;
        }
        return true;
    }

    private boolean minDistractorsFound(int size) {
        if (minDistractors > -1) {
            return size >= minDistractors;
        }
        return true;
    }

    private Optional<NGram> getFirstOccurenceOfPartOfSpeechAtMostRightTokenIn3Gram(IFIBQSentence currentSentence) {
        Optional<NGram> pos = Optional.absent();
        NGramConverter converter = new NGramConverter(3);
        List<NGram> sentence3Gram = converter.convert(currentSentence, " ");
        for (NGram threeGram : sentence3Gram) {
            if (isAnyOtherTokenThanTheRightMostAlsoSeekedPartOfSpeech(threeGram)) {
                break;
            }
            if (isRightMostTokenSeekedPartOfSpeech(threeGram)) {
                pos = Optional.of(threeGram);
                break;
            }
        }
        return pos;
    }

    private boolean isAnyOtherTokenThanTheRightMostAlsoSeekedPartOfSpeech(NGram threeGram) {
        for (int i = 0; i < threeGram.getNGramSize() - 1; i++) {
            boolean isSeekedPos = isSeekedPartOfSpeech(threeGram.getTokenAt(i));
            if (isSeekedPos) {
                return true;
            }
        }
        return false;
    }

    private boolean isRightMostTokenSeekedPartOfSpeech(NGram ngram) {
        IFIBQToken token = ngram.getTokenAt(2);
        return isSeekedPartOfSpeech(token);
    }

    public boolean isSeekedPartOfSpeech(IFIBQToken token) {
        GetPartOfSpeechVisitor visitor = new GetPartOfSpeechVisitor();
        token.accept(visitor);
        return visitor.getPartOfSpeech().equals(partOfSpeech);
    }

    /**
     * Iterates the sentence pool and looks for the specified part of speech
     * tag. If a word with the part of speech tag is found it is added to the
     * candidate pool. The determined candidates are returned in descending
     * order according to their frequency of appearance, hence the first
     * candidate is the most frequent, the second the second most frequent one,
     * etc.
     */
    private List<String> makeCandidatePool(List<IFIBQSentence> sentencePool) {
        FrequencyDistribution freqDist = new FrequencyDistribution();
        for (IFIBQSentence sentence : sentencePool) {
            GetTokenListVisitor visitor = new GetTokenListVisitor();
            sentence.accept(visitor);
            for (IFIBQToken token : visitor.getTokens()) {
                GetPartOfSpeechVisitor posVisitor = new GetPartOfSpeechVisitor();
                token.accept(posVisitor);
                if (!posVisitor.getPartOfSpeech().equals(partOfSpeech)) {
                    continue;
                }
                GetFormVisitor formVisitor = new GetFormVisitor();
                token.accept(formVisitor);
                freqDist.put(formVisitor.getForm().toLowerCase());
            }
        }
        return freqDist.getKeysDescending();
    }

    @Override
    public void setMinimum(int min) {
        this.minDistractors = min;
    }

    @Override
    public void setMaximum(int max) {
        this.maxDistractors = max;
    }

}
